(*****************************************************************************
 * Delphi Plug-in Framework (DPF)
 * Copyright (C) 2006-2007 Tao Yuan
 *
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.1 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the
 * License at http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
 * the specific language governing rights and limitations under the License.                                                               }
 *
 * The Initial Developer of the Original Code is Tao Yuan. Portions created by
 * Tao Yuan are Copyright (C) Tao Yuan (taoy.cn att gmail dott com)
 * All rights reserved.
 *****************************************************************************)

unit RdBoot;

interface

uses
  SysUtils, Forms,
  Rcl,
  RDPluginService, RDStandardPluginService, RDPlugin, RdApplicationHelp,
  RdPluginManager, RdApplicationInitializer, RdBootErrorHandler, RdApplication,
  RdServiceApplication;

const
  // This is for ResourceManager to look up resources.
  PACKAGE_NAME =
    'dpf.plugin.boot'; //$NON-NLS-1$
  (**
   * Name of the file, where to put boot error details.
   *)
  BOOT_ERROR_FILE_NAME =
    'dpf-boot-error.txt'; //$NON-NLS-1$
  // Application bootstrap configuration parameter names goes here
  PARAM_CONTROL_HOST =
    'dpf.plugin.boot.controlHost'; //$NON-NLS-1$
  PARAM_CONTROL_PORT =
    'dpf.plugin.boot.controlPort'; //$NON-NLS-1$
  PARAM_ERROR_HANDLER =
    'dpf.plugin.boot.errorHandler'; //$NON-NLS-1$
  PARAM_APP_INITIALIZER =
    'dpf.plugin.boot.appInitializer'; //$NON-NLS-1$
  PARAM_SPLASH_IMAGE =
    'dpf.plugin.boot.splashImage'; //$NON-NLS-1$

type
  TBoot = class(TObject)
  private
    class procedure bootLog(e: Exception);
    class function getAppInitializerInstance(className: string):
      IApplicationInitializer;
    class function getErrorHandlerInstance(handler: string; isServiceApp: Boolean):
      IBootErrorHandler;
    class function initApplication(errorHandler: IBootErrorHandler; props:
        TExtendedProperties): TApplicationIntfs;
    class procedure Run;
    class procedure runShell;

  public
    class procedure clearBootLog;
  protected
  public
    class procedure main;
    class procedure stopApplication(appIntfs: TApplicationIntfs);
  end;

var
  PluginService: TPluginService;
  PluginManager: TPluginManager;

implementation

uses
  Classes, RclSysUtils,
  DPFLog,
  RdDefaultApplicationInitializer, RdBootErrorHandlerConsole, RdBootErrorHandlerGui,
  RdControlThread;

class procedure TBoot.bootLog(e: Exception);
begin
  // TODO -cMM: TBoot.bootLog default body inserted
end;

class procedure TBoot.clearBootLog;
begin
  if FileExists(BOOT_ERROR_FILE_NAME) then
    DeleteFile(BOOT_ERROR_FILE_NAME);
end;

class function TBoot.getAppInitializerInstance(className: string):
  IApplicationInitializer;
begin
  if (className <> '') then
  try
    GetClass(className).Create.GetInterface(IApplicationInitializer, Result);
    Exit;
  except

  end;
  Result := TDefaultApplicationInitializer.Create;
end;

class function TBoot.getErrorHandlerInstance(handler: string; isServiceApp:
  Boolean): IBootErrorHandler;
begin
  if (handler <> '') then
  try
    GetClass(handler).Create.GetInterface(IBootErrorHandler, Result);
    Exit;
  except

  end;

  if isServiceApp then
    Result := TBootErrorHandlerConsole.Create
  else
    Result := TBootErrorHandlerGui.Create
end;

class function TBoot.initApplication(errorHandler: IBootErrorHandler; props:
    TExtendedProperties): TApplicationIntfs;
var
  appInitializer: IApplicationInitializer;
begin
  appInitializer := getAppInitializerInstance(
    props.getProperty(PARAM_APP_INITIALIZER));
  appInitializer.configure(props);
  Result := appInitializer.initApplication(errorHandler);
end;

class procedure TBoot.main;
var
  props: TExtendedProperties;
  strm: TStream;
  useControlService: Boolean;
//  appInitializer: IApplicationInitializer;
  errorHandler: IBootErrorHandler;
  controlHost: string;
  controlPort: Integer;
  controlThread: TControlThread;
  command: string;
//  app: IApplication;
  appIntfs: TApplicationIntfs;
  splashImage: string;
  applicationRoot: string;
begin
  try
    clearBootLog();
    // Load start-up configuration
    props := TExtendedProperties.Create;
    try
      strm := TFileStream.Create('boot.properties', fmOpenRead); //$NON-NLS-1$
      try
        props.load(strm);
      finally
        strm.Free;
      end;
    except
        // ignore
    end;
    useControlService := props.containsKey(PARAM_CONTROL_HOST)
      and props.containsKey(PARAM_CONTROL_PORT);
    errorHandler := getErrorHandlerInstance(
      props.getProperty(PARAM_ERROR_HANDLER), useControlService);
    try
      if (props.getProperty('applicationRoot') = '') then
      begin
          // Publish current folder as configuration parameter
          // to get it available as ${applicationRoot} variable
          // in extended properties syntax.
        applicationRoot := Rcl.FileUtils.GetApplicationDir;
        props.SetProperty('applicationRoot', applicationRoot); //$NON-NLS-1$
      end;
      if useControlService then
      begin
        controlHost := props.getProperty(PARAM_CONTROL_HOST);
        controlPort := StrToInt(props.getProperty(PARAM_CONTROL_PORT, '10'));
      end
      else
      begin
        controlHost := '';
        controlPort := 0;
      end;

      command := 'shell'; //$NON-NLS-1$
      if (ParamCount > 1) and (ParamStr(1) <> '') then
        command := Trim(ParamStr(1));

        // handle given command
      if (useControlService and Rcl.Str.IsEqual('stop', command)) then
      begin
        if (not TControlThread.stopRunningApplication(controlHost,
          controlPort)) then
          Writeln('application not running') //$NON-NLS-1$
        else
          Writeln('application stopped'); //$NON-NLS-1$

        Exit;
      end;
      if (useControlService and Rcl.Str.IsEqual('start', command)) then
      begin
        if (TControlThread.isApplicationRunning(controlHost,
          controlPort)) then
        begin
          errorHandler.handleFatalError(
            'Application already running.'); //$NON-NLS-1$
          Exit;
        end;
        appIntfs :=
          initApplication(errorHandler, props);
        if (appIntfs.App = nil) then
        begin
          System.ExitCode := 1;
          System.Exit;
          Exit;
        end;
        if (not Supports(appIntfs.App, IServiceApplication)) then
        begin
          errorHandler.handleFatalError(
            'Application is not a service.'); //$NON-NLS-1$
          Exit;
        end;
        controlThread := TControlThread.Create(controlHost,
          controlPort, appIntfs);
        appIntfs.App.startApplication(Application);
        controlThread.Resume;
        Writeln(
          'application started in BACKGROUND mode'); //$NON-NLS-1$
        Exit;
      end;
      if (useControlService and Rcl.Str.IsEqual('restart', command)) then
      begin
        if (ControlThread.stopRunningApplication(controlHost,
          controlPort)) then
          Writeln('another instance of application stopped'); //$NON-NLS-1$

        appIntfs := initApplication(errorHandler, props);
        if (appIntfs.App = nil) then
        begin
          System.ExitCode := 1;
          System.Exit;
          Exit;
        end;
        if (not Supports(appIntfs.App, IServiceApplication)) then
        begin
          errorHandler.handleFatalError(
            'Application is not a service.'); //$NON-NLS-1$
          Exit;
        end;
        controlThread := TControlThread.Create(controlHost,
          controlPort, appIntfs);
        appIntfs.App.startApplication(Application);
        controlThread.Resume;
        Writeln('app started in BACKGROUND mode'); //$NON-NLS-1$
        Exit;
      end;
        // SHELL mode by default
      if (useControlService
        and TControlThread.stopRunningApplication(controlHost,
        controlPort)) then
        Writeln('another instance of app stopped'); //$NON-NLS-1$

      splashImage := props.getProperty(PARAM_SPLASH_IMAGE);
      if (splashImage <> '') then
      begin
  //          File splashFile = new File(splashImage);
  //          if (splashFile.isFile()) {
  //              SplashWindow.splash(IoUtil.file2url(splashFile));
  //          }
      end;
      appIntfs := initApplication(errorHandler, props);
      if (appIntfs.App = nil) then
      begin
        Exit;
      end;

      Application.Initialize;
      appIntfs.App.startApplication(Application);
      Application.Run;
      stopApplication(appIntfs);
      ClearAppIntfs(appIntfs);
//        appIntfs.Clear;
//        NilNoRelease(appIntfs.App);

    except
      on e: Exception do
      begin
  //      SplashWindow.disposeSplash();
        bootLog(e);
        errorHandler.handleFatalError('Application start failed.', e); //$NON-NLS-1$
        Exit;
      end;
    end;
  finally
    FreeAndNil(props);
    FreeAndNil(controlThread);
  end;
end;

class procedure TBoot.Run;
begin
  repeat
    try
      Application.HandleMessage;
    except
      Application.HandleException(Application);
    end;
  until Application.Terminated;
end;

class procedure TBoot.runShell;
var
  c: Char;
begin
  Writeln('Press ''q'' key to exit.'); //$NON-NLS-1$
  repeat
    try
      Readln(c);
    except
      break;
    end;
    if (('q' = c) or ('Q' = c)) then
      break;
  until (true);
end;

class procedure TBoot.stopApplication(appIntfs: TApplicationIntfs);
var
//  pluginManager: TPluginManager;
  appService: IServiceApplication;
begin
  if Supports(appIntfs.App, IServiceApplication, appService) then
  begin
    appService.stopApplication;
    appService := nil;
  end;

  try
//    PluginManager := TPluginManager.lookup(appIntfs.PluginInfo);
//    appIntfs.Release;
    ReleaseAppIntfs(appIntfs);
    if (PluginManager <> nil) then
      PluginManager.shutdown();

    PluginService.Free;

    LogFactory.getLogger(TBoot.ClassName).info('logging system finalized'); //$NON-NLS-1$
    LogFactory.getLogger(TBoot.ClassName).info('---------------------------------'); //$NON-NLS-1$
  except
  end;
end;

end.

